//
//  RFPhotos.swift
//  LSTools
//
//  Created by Roffa Zhou on 2021/12/20.
//

import Foundation
import Photos
import UIKit

@objcMembers
public class RFAsset: NSObject{
    public var url: String?   //图片路径，存储在本地沙盒的路径
    public var mediaType: Int8 = 0  //文件类型  0未知  1图片 2视频
    public var width: Int16 = 0       //图片宽度 0代表未知
    public var height: Int16 = 0
    public var creationDate: TimeInterval = 0  //创建的时间戳
    public var modificationDate: TimeInterval = 0  //更改的时间戳
    public var duration: Int = 0          //持续时间 图片返回0  单位ms
    public var videoIcon: String? // 视频的图片
}

@objcMembers
public class RFPhotos: NSObject{
    public enum AuthorStatus: Int{
        case no       //未开始授权
        case fail     //授权失败
        case succ     //授权成功 允许访问所有相册
        case limit    //授权成功，允许访问部分
    }
   
    
    public static let photosPath = LSPathManager.prefixDocPath      //存储在本地的路径
    
    static let shared = RFPhotos()
    private override init() {
        super.init()
    }
    
    /// 获取相册授权状态
    /// - Parameter completion: 授权结果
    /// - Parameter onlyAdd  是否仅获取相册写入权限  ios14+有效
    /// - Note  获取的授权状态为对相册的读写操作权限
    /// - Authors rf/2021-12-20

    public class func getAuthor(_ onlyAdd:Bool = false, completion: (AuthorStatus)->Void){
        var status: PHAuthorizationStatus  //授权状态
        if #available(iOS 14, *) {
            if onlyAdd{
                status = PHPhotoLibrary.authorizationStatus(for: .readWrite )
            }else{
                status = PHPhotoLibrary.authorizationStatus(for: .addOnly )
            }
        } else {
            // Fallback on earlier versions
            status = PHPhotoLibrary.authorizationStatus()
        }
        if status == .restricted || status == .denied {
            completion(.fail)
        }else if status == .notDetermined{
            completion(.no)
        }else if #available(iOS 14, *), status == .limited {
            completion(.limit)
        }
        else{
            completion(.succ)
        }
    }
    
    /// 发起相册权限请求
    /// - Parameter completion: 授权结果
    /// - Note 当已经发起过相册权限，将不会二次发起权限请求
    /// - Authors rf/2021-12-20
    public class func requestAuthor(_ onlyAdd:Bool = false, _ completion:@escaping (AuthorStatus)->Void){
        getAuthor(onlyAdd){ status  in
            if status == .no {
                PHPhotoLibrary.requestAuthorization({ (status) in
                    if status == .authorized {
                        completion(.succ)
                    }else if #available(iOS 14, *), status == .limited {
                        completion(.limit)
                    } else {
                        completion(.fail)
                    }
                })
            }else{
                completion(status)
            }
        }
    }
    
    /// 获取指定相册的所有图片信息
    /// - Parameters:
    ///   - ablumName: 相册名 不传获取所有图片信息
    ///   - descending: 是否根据时间逆袭排序输出
    ///   - isSave: 是否将相册图片同步存储到本地沙盒 默认为true.
    ///   - callback：[RFAsset]参数需要有值时， isSave必须为true, 其他正常读取时，isSave=false； lSDictionary中存储出错信息
    /// - Note  存储沙盒由于会带来内存激增，如果一次需要存储在本地的资源过多，需重复调用转换方法，每次转换内部限流50个资源
    /*  {"code":-401, "message":"access to photo library denied" }
    * {"code":-1, "message":"customDirName cannot be empty" }
    * {"code":-2, "message":"get album info fail" }
    * SDK-LEVEL 10004
     */
    @discardableResult
    public class func getAllAssetInAlbum(_ ablumName: String? = nil, descending: Bool = false, isSave:Bool=true, callback: (([RFAsset]?, LSDictionary?) -> Void)? = nil ) -> [PHAsset]{
        var assets: [PHAsset] = []
        if isSave{ //需要存储到本地使用场景必须传 相册名
            if callback != nil {
                if ablumName == nil{
                    callback!(nil, ["code":-1, "message":"customDirName cannot be empty"])
                    return assets
                }
            }
        }
        var bCan = true  //是否能正常读取到相册图片
        getAuthor { status  in
            if status == .fail || status == .no{ //权限未开启
                if callback != nil {
                    callback!(nil, ["code":-401, "message":"access to photo library denied"])
                }
                bCan = false
            }
        }
        if !bCan {
            return assets
        }
        
        let option = PHFetchOptions()
        if ablumName != nil {
            option.predicate = NSPredicate(format: "title = %@", ablumName!)
        }
        let result: PHAssetCollection? = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: option).firstObject
        //排序跳转
        let option2 = PHFetchOptions()
        option2.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: !descending)]
        
        guard let collection = result else{
            if callback != nil {
                callback!(nil, ["code":-2, "message":"get album info fail"])
            }
            return assets
        }
        let fetch = PHAsset.fetchAssets(in: collection, options: option2)
        var noCacheAssets  = [PHAsset]()        //本地未缓存的资源
        fetch.enumerateObjects { (asset, index, stop) in
            assets.append(asset)
            if asset.mediaType == .image{
                if !LSPathManager.bExist(path: "\(asset.localIdentifier).jpg"){  //本地不存在缓存，记录
                    noCacheAssets.append(asset)
                }
            }else if asset.mediaType == .video{
                if !LSPathManager.bExist(path: "\(asset.localIdentifier).mp4"){  //本地不存在缓存，记录
                    noCacheAssets.append(asset)
                }
            }
        }
        if isSave {
            //本地沙盒存储一份文件信息, 如本地已经存储过，不在进行转换为UIImage再存储到本地，避免瞬时内存激增
            convertPHAssetsToImages(noCacheAssets, onlySave: true) { images in
                if let closure = callback {
                    closure(convertPHAssetsToRFAssets(assets), nil)
                }
            }
        }
        
        return assets
    }
    
    /// 将PHAsset对象转换为RFAsset对象输出
    /// - Parameters:
    ///   - assets: [PHAsset]]
    ///   - ablumName: 本地相册名
    /// - Returns: 转换后数组
    /// - Note 转换后的url对应为本地路径的url，需要确保本地路径存在真实的文件,
    /// - Authors rf/2021-12-21
    public class func convertPHAssetsToRFAssets(_ assets: [PHAsset]) -> [RFAsset]{
        
        var newAssets = [RFAsset]()
        for asset in assets{
            let newAsset = RFAsset()
            newAsset.width = Int16(asset.pixelWidth)
            newAsset.height = Int16(asset.pixelHeight)
            newAsset.mediaType = Int8(asset.mediaType.rawValue)
            newAsset.creationDate = asset.creationDate?.timeIntervalSince1970 ?? 0
            newAsset.modificationDate = asset.modificationDate?.timeIntervalSince1970 ?? 0
            newAsset.duration = Int(asset.duration * 1000)
            if asset.mediaType == .image{
                newAsset.url = LSPathManager.path("\(asset.localIdentifier).jpg")
                newAsset.videoIcon = ""
            }else if asset.mediaType == .video {
                newAsset.url = LSPathManager.path("\(asset.localIdentifier).mp4")
                newAsset.videoIcon = LSPathManager.path("\(asset.localIdentifier).jpg")
            }
            newAssets.append(newAsset)
        }
        
        return newAssets
    }
    
    /// 将PHAsset转换为UIImage对象返回
    /// - Parameters:
    ///   - assets: [PHAsset]
    ///   - isSave: 是否保存到本地沙盒，默认true
    ///   - onlySave: 是否只将图片保留在本地，当为true时，complete返回的数组为空
    ///   - complete: 返回[UIImage]
    /// - Note
    public class func convertPHAssetsToImages(_ assets: [PHAsset], isSave: Bool = true, onlySave:Bool = false, complete:@escaping (([UIImage]) -> Void)) {
        var images = [UIImage]()
        
        let options = PHImageRequestOptions()
        options.isNetworkAccessAllowed = true
        options.resizeMode = .fast
        options.deliveryMode = .highQualityFormat
        
        let groupQueue = DispatchQueue(label: "save_doc_photo_queue")
        groupQueue.async {
            let sema = DispatchSemaphore(value: 0)
            var i = 0
            for asset in assets {
                autoreleasepool {
                    i += 1
                    if asset.mediaType == .image {
                        convertPHAssetToImage(asset) { img in
                            if img == nil{
                                sema.signal()
                                complete(images)
                                return
                            }
                            if !onlySave{
                                images.append(img!)
                            }
                            sema.signal()
                        }
                        sema.wait()
                    }else if asset.mediaType == .video{
                        if isSave {  //本地需要存储，转换为视频流存储在本地，返回存储后的路径，如存储失败返回相册路径
                            convertPHAssetToVideo(asset){ url in
                                //视频文件需要同时存储一份视频预览图
                                convertPHAssetToImage(asset) { img in
                                    if img == nil{
                                        sema.signal()
                                        complete(images)
                                        return
                                    }
                                    if !onlySave{
                                        images.append(img!)
                                    }
                                    sema.signal()
                                }
                            }
                            sema.wait()
                        }
                    }
                }
            }
            
            DispatchQueue.main.async {
                complete(images)
            }
        }
        
        return
    }
    
    /// 将PHAsset转换为图片
    /// - Parameters:
    ///   - asset: PHAsset
    ///   - isSave: 转换的同时，是否保存到本地沙盒，默认保存
    ///   - completion: 转换成功返回UIImage对象，失败返回nil
    /// - Authors: rf/2021-12-23
    public class func convertPHAssetToImage(_ asset: PHAsset, isSave: Bool = true, completion:@escaping (UIImage?)->Void){
        let options = PHImageRequestOptions()
        options.isNetworkAccessAllowed = true
        options.resizeMode = .fast
        options.deliveryMode = .highQualityFormat
        PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, a in
            guard let image = image else {
                completion(nil)
                return
            }
            if isSave {
                saveImageToCache(image, localId: asset.localIdentifier)
            }
            completion(image)
        }
    }
    /// 转换phasset为视频流
    /// - Parameters:
    ///   - asset: PHAsset
    ///   - isSave: 是否本地保存视频流
    ///   - completion: 如果isSave=true，则返回的url为本地路径，否则为对应相册的URL， 获取视频失败返回nil
    public class func convertPHAssetToVideo(_ asset: PHAsset, isSave: Bool = true, completion:@escaping (URL?)->Void){
        let options = PHVideoRequestOptions()
        options.deliveryMode = .highQualityFormat
        PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avasset , audio , dict  in
            if let urlasset = avasset as? AVURLAsset{
                let data = try? Data(contentsOf: urlasset.url)
                if isSave{
                    if data != nil{
                        let str = saveVideoToCache(data!, localId: asset.localIdentifier)
                        completion(URL(string: str))
                    }else{
                        completion(urlasset.url)
                    }
                }else{
                    completion(urlasset.url)
                }
            }else{
                completion(nil)
            }
        }
    }
    /// 保存视频到本地路径
    /// - Parameters:
    ///   - img: uiimage对象
    ///   - localId: 存储在本地对应的唯一标识，如果是从本地相册读取，需要用存储时的唯一id作为图片名
    @discardableResult
    private class func saveVideoToCache(_ data: Data, localId: String) -> String{
        LSPathManager.create("\(localId).mp4", data: data)
        return LSPathManager.path("\(localId).mp4") ?? ""
    }
    public class func delVideoToCache(_ name: String){
        let array = name.components(separatedBy: "/")
        if array.count > 0{ //由于本地相册id会包含/，检测包含/直接删除最上级
            LSPathManager.del(array.first!)
            return
        }
        LSPathManager.del("\(name).mp4")
    }
    
    /// 保存图片到本地路径
    /// - Parameters:
    ///   - img: uiimage对象
    ///   - localId: 存储在本地对应的唯一标识，如果是从本地相册读取，需要用存储时的唯一id作为图片名
    private class func saveImageToCache(_ img: UIImage, localId: String){
        LSPathManager.create("\(localId).jpg", data: img.jpegData(compressionQuality: 0.5))
    }
    public class func delImageToCache(_ name: String){
        let array = name.components(separatedBy: "/")
        if array.count > 0{ //由于本地相册id会包含/，检测包含/直接删除最上级
            LSPathManager.del(array.first!)
            return
        }
        LSPathManager.del("\(name).jpg")
    }
    /// 删除图片资源
    /// - Parameters:
    ///   - assets: 图片数组
    ///   - completion: 结果， 成功或失败
    /// - Note 泛型在OC上不支持，支持[PHAsset]/[RFAsset]/[String]
    /// - Authors rf/2021-12-20
    public class func deleteAssets(_ assets: [NSObject], completion:@escaping (Bool)->Void){
        PHPhotoLibrary.shared().performChanges {
            if let phassets = assets as? [PHAsset]{
                for asset in phassets{  //同步删除本地沙盒中存在的文件
                    delImageToCache(asset.localIdentifier)
                }
                PHAssetChangeRequest.deleteAssets(phassets as NSFastEnumeration)
            }else if let rfassets = assets as? [RFAsset]{
                let urls = rfassets.map { (asset) -> String in
                    if let url = asset.url{
                        delImageToCache(url)
                        let path = url.replacingOccurrences(of: photosPath, with: "")
                        let uri = URL(string: path)
                        return uri?.deletingPathExtension().absoluteString ?? ""
                    }
                    return ""
                }
                let phassets = PHAsset.fetchAssets(withLocalIdentifiers:urls, options: PHFetchOptions())

                PHAssetChangeRequest.deleteAssets(phassets as NSFastEnumeration)
            }else if let rfassets = assets as? [String]{
                //将原完整路径url转换为localIdentifiers
                let urls = rfassets.map { (url) -> String in
                    delImageToCache(url)
                    let path = url.replacingOccurrences(of: photosPath, with: "")
                    let uri = URL(string: path)
                    return uri?.deletingPathExtension().absoluteString ?? ""
                    return ""
                }
                let phassets = PHAsset.fetchAssets(withLocalIdentifiers:urls, options: PHFetchOptions())

                PHAssetChangeRequest.deleteAssets(phassets as NSFastEnumeration)
            }
            
        } completionHandler: { succ , err  in
            DispatchQueue.main.async {
                if succ {
                    completion(true)
                }else{
                    completion(false)
                }
            }
        }
       
    }
    
}
